VGA-Kurs - Part #8 Hallo! Leider muáte 'T.C.P.'s Beginner's Guide To VGA Coding' in der letzten Ausgabe ausfallen, weil ich keine Zeit hatte, aber diesmal gibt es wieder einen neuen Teil, und zwar den 8.! Und der behandelt auf vielfachen Wunsch (von 2 Lesern) das Thema 'Plasmas'. Erstmal eine kleine Erkl„rung fr die, die noch nie solch einem Effekt begegnet sind: Es gibt zwei Arten von Plasmas. Das Colorcycle- und das Realtime- oder auch Sinus-Plasma. Beim Colorcycle-Plasma handelt es sich um ein nach einer bestimmten Formel vorberechnetes, buntes Bild, dessen Farben einfach per Palettenrotation vertauscht werden. Um das Bild zu berechnen bedient man sich der Iteration. Es werden also 9 Punkte (8 am Rand, einer in der Mitte) zuf„lliger Farbe auf den Screen gesetzt, so daá sie ihn in 4 Rechtecke teilen. Nun verf„hrt man mit diesen Rechtecken genauso wie mit dem Screen am Anfang, nur daá die neuen Punkte aus den alten durch Interpolation errechnet werden. Da diese Art von Plasma aber sowieso die uninteressantere ist, hier nur schnell der Source und dann ohne groáe Erkl„rungen zum Sinus-Plasma. uses crt; var n : byte; Palette : array[0..767] of byte; function newcol(mc,n,dvd:integer) : byte; begin newcol := ((mc+n-random(n)) div dvd) mod 192; end; procedure subdivide(x1,y1,x2,y2:word); var x,y,dxy,n1,n2,n3,n4 : word; begin if (x2-x1 < 2) and (y2-y1 < 2) then exit; x := (x2+x1) div 2; y := (y2+y1) div 2; n1 := mem[$A000:320*y1+x1]; n2 := mem[$A000:320*y2+x1]; n3 := mem[$A000:320*y1+x2]; n4 := mem[$A000:320*y2+x2]; dxy := 5 * (x2-x1+y2-y1) div 3; if mem[$A000:320*y1+x] = 0 then mem[$A000:320*y1+x] := newcol(n1+n3,dxy,2); if mem[$A000:320*y+x1] = 0 then mem[$A000:320*y+x1] := newcol(n1+n2,dxy,2); if mem[$A000:320*y+x2] = 0 then mem[$A000:320*y+x2] := newcol(n3+n4,dxy,2); if mem[$A000:320*y2+x] = 0 then mem[$A000:320*y2+x] := newcol(n2+n4,dxy,2); mem[$A000:320*y+x] := newcol(n1+n2+n3+n4,dxy,4); subdivide(x1,y1,x,y); subdivide(x,y1,x2,y); subdivide(x1,y,x,y2); subdivide(x,y,x2,y2); end; procedure SetPal(col,r,g,b:byte); begin port[$3C8] := col; port[$3C9] := r; port[$3C9] := g; port[$3C9] := b; end; procedure RotatePalette; var Temp : array[0..2] of byte; begin repeat move(Palette,Temp,3); move(Palette[3],Palette[0],765); move(Temp,Palette[765],3); for n := 0 to 255 do setpal(n,Palette[n*3],Palette[n*3+1],Palette[n*3+2]); until keypressed; end; begin randomize; asm mov ax,13h; int 10h end; { In Mode 13h schalten } fillchar(Palette,768,0); { Palette erstellen } for n := 0 to 255 do begin Palette[n*3+1] := n div 2; Palette[n*3+2] := n; setpal(n,0,n div 2,n); end; subdivide(0,0,319,199); { Plasma aufbauen } RotatePalette; { Palette rotieren } readkey; asm mov ax,3; int 10h end; { Zurck zum Textmodus } end. Der Grund, warum dieses Plasma eher langweilig wirkt, ist das Fehlen einer Bewegung. Auáerdem w„ren etwas rundere Formen angenehm. Diese M„ngel behebt das Sinus-Plasma. Sein Nachteil ist allerdings, daá es sehr viel mehr Rechenzeit erfordert, deshalb sollte es m”glichst in Assembler geschrieben werden. Doch wie macht man das berhaupt? Zuerst ben”tigt man eine (Co)Sinus-Tabelle. Diese entscheidet nachher die Form des Plasmas, allerdings nicht allein. Auch wie die Werte aus dieser Tabelle entnommen werden, ist relevant. Als Beispiel benutzen wir hier die Formel Farbwert := SinTab[Wert1] + SinTab[Wert2] + SinTab[Wert3] + SinTab[Wert4] Die vier Indizes sind Werte, die bei jedem Durchlauf um einen bestimmten oder zuf„lligen Wert erh”ht werden, wodurch eine Bewegung zustande kommt. Da wir die Variablen innerhalb der Loops ver„ndern, mssen wir sie vorher in Hilfsvariablen (TWert1-4) sichern. Die Variablen Wert1 und Wert2 sind fr das horizontale und Wert3 und Wert4 fr das vertikale Muster des Plasmas verantwortlich. Sie werden in den entsprechenden Loops um feste Werte erh”ht, die die Gr”áe der Plasma-Kreise bestimmen. Das Ganze nun erstmal in einer 100%-Pascal-Version: uses crt; var Wert1,Wert2,Wert3,Wert4 : byte; TWert1,TWert2,TWert3,TWert4 : byte; SinTab : array[0..255] of byte; n1,n2 : word; col : byte; procedure SetPal(col,r,g,b:byte); begin port[$3C8] := col; port[$3C9] := r; port[$3C9] := g; port[$3C9] := b; end; procedure CalcSinus(Ofs,Amp:byte;Len,Par:word); begin for n1 := 0 to Len do SinTab[n1] := round(sin(n1/Par*pi*Len/180*2)*Amp)+Ofs; end; begin CalcSinus(32,31,255,360); asm mov ax,13h; int 10h end; for n1 := 0 to 127 do begin setpal(n1,n1 div 6,n1 div 3,n1 div 3); setpal(255-n1,n1 div 6,n1 div 3,n1 div 3); end; repeat TWert3 := Wert3; TWert4 := Wert4; for n1 := 0 to 319 do begin TWert1 := Wert1; TWert2 := Wert2; for n2 := 0 to 199 do begin col := SinTab[TWert1] + SinTab[TWert2] + SinTab[TWert3] + SinTab[TWert4]; { Farbwert berechnen } mem[$A000:n2*320+n1] := col; inc(TWert1,4); inc(TWert2,3); end; inc(TWert3,4); inc(TWert4,5); end; dec(Wert1,4); { Bewegung des Plasmas } inc(Wert3,4); inc(Wert1,random(1)); { Zufallswerte bewirken, daá das Plasma } dec(Wert2,random(2)); { etwas unregelm„áig wird } inc(Wert3,random(1)); dec(Wert4,random(2)); until keypressed; readkey; asm mov ax,3; int 10h end; end. Wer nicht gerade einen schnellen 486 hat, wird wohl mit der Geschwindigkeit nicht ganz zufrieden sein, deshalb jetzt nochmal die Repeat-Schleife in der Assembler-Version: { Einfach die Repeat-Schleife aus dem oberen Listing l”schen und diese Zeilen einfgen: } asm mov ax,0A000h { VGA-Segment nach ES } mov es,ax @mainloop: xor di,di { Bildschirmoffset auf 0 } mov al,Wert3 { Werte sichern } mov TWert3,al mov al,Wert4 mov TWert4,al mov n1,0 { Z„hler initialisieren } @loop1: mov al,Wert1 { Werte sichern } mov TWert1,al mov al,Wert2 mov TWert2,al mov n2,0 { Z„hler initialisieren } @loop2: xor bx,bx { Farbwert wird in BX berechnet } mov al,TWert4 { Index holen } xor ah,ah mov si,ax mov al,[si+offset SinTab] { Wert auslesen } add bx,ax { und auf BX addieren } mov al,TWert3 mov si,ax mov al,[si+offset SinTab] add bx,ax mov al,TWert2 mov si,ax mov al,[si+offset SinTab] add bx,ax mov al,TWert1 mov si,ax mov al,[si+offset SinTab] add bx,ax mov es:[di],bl { Pixel setzen } inc di { Bildschirmoffset erh”hen } add TWert1,4 { Indizes erh”hen } add TWert2,3 inc n2 cmp n2,320 jne @loop2 add TWert3,4 add TWert4,5 inc n1 cmp n1,200 jne @loop1 sub Wert1,4 add Wert3,4 mov ah,0Bh int 21h or al,al jz @mainloop end; Man kann nun das Aussehen des Plasmas ver„ndern, indem man z.B. das Offset und die Amplitude der Sinustabelle beim Aufruf von CalcSinus „ndert. Auáerdem k”nnte man hier parallel noch eine Palettenrotation einbauen, das kostet nicht viel Rechenzeit, sieht aber um so besser aus. Dieses Beispiel ist natrlich nur eine von vielen Methoden, Sinus-Plasmas zu coden. Es wurden schon einige hier im PCH abgedruckt, wenn ihr sie noch habt, seht sie euch doch nochmal an, ihr erfahrt dort bestimmt noch einiges mehr. (Hey, an dieser Stelle hat der VGA-Kurs das 100.000ste Byte erreicht!!!) Dieser Teil des Kurses ist noch nicht zu Ende! Es folgt nun ein Bonusteil, und zwar ber Fraktale, wie Onkel Joe es sich im PCH 3/96 gewnscht hat. (Ich h„tte nichts gegen weitere Bonusteile, wenn euch also ein Randthema der VGA-Programmierung interessiert, schreibt mir, und ich werde mal sehen, ob ich dazu was schreiben kann.) Jeder kennt sie, jeder ist fasziniert von ihnen: Fraktale. Doch wie werden diese Dinger eigentlich berechnet? Das funktioniert so „hnlich wie bei der Berechnung der Colorcycle-Plasmas, allerdings kommt man hier nicht um Real-Zahlen herum, was sich nicht unerheblich auf die Berechnungsgeschwindigkeit auswirkt. Wie beim Plasma (das brigens auch ein Fraktal ist) wird mit Iteration gearbeitet, d.h. daá die Werte, die im letzen Schleifendurchlauf berechnet wurden, als Basis fr die neuen Berechnungen herangezogen werden. Der folgende Source behandelt die beiden popul„rsten Fraktal-Typen, Mandelbrot und Julia. Fr die beiden Typen ist jeweils eine Prozedur zust„ndig, die aus den X- und Y-Koordinaten des Pixels den zugeh”rigen Farbwert des Fraktals berechnet. Ihr k”nnt die Berechnungsweise aus den Prozeduren entnehmen, die Erkl„rung dieser Formeln liegt leider auáerhalb meines mathematischen Wissensstandes. {$N+,E+} uses crt; { Diese Konstanten k”nnen ver„ndert werden, um das Aussehen der Fraktale zu bestimmen. } const Colors = 32; { Anzahl der Farben des Fraktals } Width = 320; { Breite des Fraktals } Height = 200; { H”he des Fraktals } Limit = 8.0; { Berechnungsgrenze, regelt Sch„rfe } XRMin = -2.0; { Linke Grenze des Fraktals } XRMax = 1.0; { Rechte Grenze des Fraktals } YRMin = -1.3; { Obere Grenze des Fraktals } YRMax = 1.3; { Untere Grenze des Fraktals } type real = double; { Zur Optimierung der Flieákomma-Berechnungen } var XPos,YPos : word; RealP,ImagP : real; CurrX,CurrY : real; a2,b2 : real; n : byte; function CalcColorMandel(XPos,YPos:word) : byte; begin CurrX := XPos / Width * (XRMax-XRMin) + XRMin; CurrY := YPos / Height * (YRMax-YRMin) + YRMin; RealP := 0; ImagP := 0; n := 0; repeat a2 := sqr(RealP); b2 := sqr(ImagP); ImagP := 2 * RealP * ImagP + CurrY; RealP := a2 - b2 + CurrX; inc(n); until (n >= Colors) or (a2+b2 >= Limit); CalcColorMandel := n - 1; end; function CalcColorJulia(XPos,YPos:word) : byte; begin CurrX := 0.07; CurrY := -0.4; RealP := -2 + 0.5 + XPos / (Width/3); ImagP := 2 - 0.5 - YPos / (Height/3); n := 0; repeat a2 := sqr(ImagP) - sqr(RealP) + CurrX; b2 := 2 * RealP * ImagP + CurrY; RealP := a2; ImagP := b2; inc(n); until (n >= Colors) or (sqr(a2)+sqr(b2) > 4) or (abs(a2) > 2) or (abs(b2) > 2); CalcColorJulia := n - 1; end; begin asm mov ax,13h; int 10h end; { In Mode 13h schalten } for YPos := 0 to Height-1 do { Mandelbrot-Menge aufbauen } for XPos := 0 to Width-1 do mem[$A000:YPos*320+XPos] := CalcColorMandel(XPos,YPos); readkey; for YPos := 0 to Height-1 do { Julia-Menge aufbauen } for XPos := 0 to Width-1 do mem[$A000:YPos*320+XPos] := CalcColorJulia(XPos,YPos); readkey; asm mov ax,3; int 10h end; { Zurck zum Textmodus } end. So, wir sind mal wieder am Ende angelangt, und es folgt die Vorschau auf den n„chsten Teil: Ich will nicht zu viel versprechen, aber ich werde beim n„chsten mal mit 3D-Grafik beginnen. Dieses Thema ist wirklich nicht leicht anschaulich zu machen, vor allem Leser ohne Mathekenntnisse (Geometrie!) werden einige Probleme haben, aber ich werde es trotzdem versuchen. Also, bis bald! [ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ] [ ] [ No part of this document may be reproduced, transmitted, ] [ transcribed, stored in a retrieval system, or translated into any ] [ human or computer language, in any form or by any means; electronic, ] [ mechanical, magnetic, optical, chemical, manual or otherwise, ] [ without the expressed written permission of the author. ] [ ] [ The information contained in this text is believed to be correct. ] [ The text is subject to change without notice and does not represent ] [ a commitment on the part of the author. ] [ The author does not make a warranty of any kind with regard to this ] [ material, including, but not limited to, the implied warranties of ] [ merchantability and fitness for a particular purpose. The author ] [ shall not be liable for errors contained herein or for incidental or ] [ consequential damages in connection with the furnishing, performance ] [ or use of this material. ]